package org.smth.search.persistence.impl;

import com.google.inject.Inject;
import com.google.inject.Singleton;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;

@Singleton
public class JpaDao {

	@Inject
	private EntityManagerFactory factory;

	private ThreadLocal<EntityManager> managerLocal = new ThreadLocal<EntityManager>();

	private ThreadLocal<EntityTransaction> transactionLocal = new ThreadLocal<EntityTransaction>();

	public <V> V readonlyCall(JpaCallback<V> callback) {
		return myCall(callback, false);
	}

	public <V> V updateCall(JpaCallback<V> callback) {
		return myCall(callback, true);
	}

	public void persist(final Object o) {
		updateCall(new JpaCallback<Object>() {
			public Object call(EntityManager manager) {
				manager.persist(o);
				return null;
			}
		});
	}

	public void remove(final Object o) {
		updateCall(new JpaCallback<Object>() {
			public Object call(EntityManager manager) {
				manager.remove(o);
				return null;
			}
		});
	}

	private <V> V myCall(JpaCallback<V> callback, boolean haveTransaction) {
		EntityManager manager_ = managerLocal.get();
		EntityManager manager;
		if (manager_ == null) {
			manager = factory.createEntityManager();
			managerLocal.set(manager);
		} else {
			manager = manager_;
		}
		EntityTransaction transaction_ = transactionLocal.get();
		EntityTransaction transaction;
		if (haveTransaction && transaction_ == null) {
			transaction = manager.getTransaction();
			transactionLocal.set(transaction);
			transaction.begin();
		} else {
			transaction = transaction_;
		}
		try {
			V result = callback.call(manager);
			if (haveTransaction && transaction_ == null) {
				transaction.commit();
			}
			return result;
		} catch (Throwable t) {
			if (haveTransaction && transaction_ == null) {
				transaction.rollback();
			}
			RuntimeException e;
			if (t instanceof RuntimeException) {
				e = (RuntimeException) t;
			} else {
				e = new RuntimeException(t);
			}
			throw e;
		} finally {
			if (haveTransaction && transaction_ == null) {
				transactionLocal.remove();
			}
			if (manager_ == null) {
				managerLocal.remove();
				manager.close();
			}
		}
	}

}
